#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include <string>

#include "json.hpp"

// 函数声明
extern int doSocket(const char *host, int clientPort);
extern void benchCore(const char *host, const int port);
extern void myhandler(int num);
extern void setNonblock(int sockfd);
// 全局变量
int speed = 0;
int fail = 0;
int benchTime = 10;
int clients = 1;
int myPipe[2];
int timerexpired = 0;
int force = 0;  // 是否要读取数据
int16_t netPort = 9999;
char netIPAddr[16] = "127.0.0.1";
// 给json用的
const std::string MSG_TYPE_ID = "msgid";
const std::string SEND_ID = "id";
const std::string NAME = "name";
const std::string STATE = "state";
const std::string PWD = "password";
const std::string ERRNO = "errno";
const int LOG_IN = 1;
const int LOG_IN_MSG_ACK = 2;
const int REG = 3;
const int REG_MSG_ACK = 4;

/*************** main ******************/

int main(int argc, char const *argv[]) {
    // step 1 : 创建管道
    int ret = pipe(myPipe);
    if (ret < 0) {
        printf("make pipe failed\n");
        return 0;
    }
    // step 2 : 先测试能否建立socket连接
    // host 和 clientPort 后期应该修改为自定义输入
    int testSock = doSocket(netIPAddr, netPort);
    if (testSock < 0) {
        printf("connect to peer failed\n");
        return 0;
    }
    close(testSock);
    // step 3 : 创建<clients>个子进程
    int pid = -1;
    for (int i = 0; i < clients; ++i) {
        pid = fork();
        // 先把所有的子进程创建出来
        if (pid <= 0) {
            sleep(1);
            // 跳出for循环，只使用父进程创建一个个子进程
            break;
        }
    }
    if (pid < 0) {
        printf("fork failed!\n");
        return 0;
    }
    // 子进程
    else if (pid == 0) {
        /* 执行benchcore */
        benchCore(netIPAddr, netPort);

        /* 写入到管道中 */
        FILE *fptr = fdopen(myPipe[1], "w");
        if (fptr == nullptr) {
            printf("fdopen fd failed\n");
            return -1;
        }
        fprintf(fptr, "%d %d\n", speed, fail);
        /* 关闭文件指针 */
        fclose(fptr);
        /* 退出子进程 */
        return 0;

    }
    // 父进程
    else if (pid > 0) {
        /* 读取管道 */
        FILE *fptr = fdopen(myPipe[0], "r");
        if (fptr == nullptr) {
            printf("fdopen fd failed\n");
            return -1;
        }

        /* 关闭文件读写的缓冲区（C语言的高级读写函数的特性） */
        setvbuf(fptr, NULL, _IONBF, 0);

        /* 读取文件中的数据 */
        int s = 0, f = 0;

        /* 累加数据 */
        while (1) {
            int cnt = fscanf(fptr, "%d %d", &s, &f);
            if (cnt < 2) {
                fprintf(stderr, "数据读取有问题\n");
                break;
            }
            speed += s;
            fail += f;
            if (--clients == 0) {
                break;
            }
        }
        fclose(fptr); /* 记得关闭文件指针 */
        /* 打印结果 */
        printf("成功数 ：%d\n失败数 ：%d\n", speed, fail);

        return 0;
    }
    return 0;
}

// 函数的定义
int doSocket(const char *host, int clientPort) {
    // 使用socket函数，连接host和clientPort对应的服务器
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        printf("socket failded!\n");
        return -1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(clientPort);
    int ret = inet_pton(AF_INET, host, &addr.sin_addr.s_addr);
    if (ret != 1) {
        printf("inet_pton convert address failed!\n");
        return -1;
    }
    ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret < 0) {
        printf("connect failed\n");
        return -1;
    }
    // 连接成功了
    return fd;
}

// 在子进程中工作的bench核心函数
void benchCore(const char *host, const int port) {
    // step 1 : 注册信号捕捉函数
    struct sigaction sa = {0};
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    // 绑定对应的动作函数
    sa.sa_handler = myhandler;
    // 捕捉的是闹钟信号
    sigaction(SIGALRM, &sa, NULL);
    // 启动定时器
    alarm(benchTime);
    // step 2 : 执行业务逻辑
    int sockfd = -1;
    char buf[1024] = {0};
    // step 3 : 创建JSON对象
    nlohmann::json ms;
    ms[MSG_TYPE_ID] = REG; /* 注册业务 */
    ms[PWD] = "123456";    /* 密码统一设置为123456 */
    int nameInt = 2;
    char req[128] = {0};
    while (1) {
        if (timerexpired) {
            // 定时时间到
            return;
        }
        // 组装响应报文
        ms[NAME] = std::to_string(nameInt++);
        memset(req, 0, sizeof(req));
        strncpy(req, ms.dump().c_str(), sizeof(req));
        // 建立socket连接
        sockfd = doSocket(host, port);
        if (sockfd < 0) {
            fail++;
            continue;
        }
        // 获取发送数据包的长度，并发送数据
        int reqlen = strlen(req);
        int wlen = write(sockfd, req, reqlen);
        if (reqlen != wlen) {
            fail++;
            close(sockfd);
            continue;
        }
        // 需要读取服务器发送来的消息吗？
        if (force == 0) {
            // // 设置为非阻塞I/O
            // setNonblock(sockfd);
            // 读取服务器返回的报文
            int len = read(sockfd, buf, sizeof(buf));
            if (len < 0) {
                // 阻塞I/O读取失败
                fail++;
                close(sockfd);
                sockfd = -1;
            } else if (len == 0) {
                // 阻塞I/O对端关闭了连接
                printf("对端关闭了连接\n");
            } else {
                // 数据读取成功, 调用JSON进行解析
                // printf("buf size : %d buf : %s\n", strlen(buf), buf);
                nlohmann::json response =
                    nlohmann::json::parse(std::string(buf));
                if (response.count(ERRNO) == 0 || response[ERRNO] == -1) {
                    /* 注册业务没有成功 */
                    fail++;
                    close(sockfd);
                    sockfd = -1;
                }
            }
        }
        if (sockfd) {
            int ret = close(sockfd);
            if (ret != 0) {
                fail++;
            } else{
                speed++;
            }
        }
    }
}
// 信号对应动作函数
void myhandler(int num) { timerexpired = 1; }
// 设置非阻塞I/O
void setNonblock(int sockfd) {
    int oldflag = fcntl(sockfd, F_GETFL);
    int newflag = oldflag | O_NONBLOCK;
    fcntl(sockfd, F_SETFL, newflag);
}